<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>2048 Game</title>
    <style>
      body {
        font-family: 'avenir', sans-serif;
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100vh;
        margin: 0;
        background-color: #ffffff;
      }
      .container {
        text-align: center;
        width: 468px;
      }
      h1 {
        color: #333;
        font-size: 1.5em;
      }
      .grid {
        margin: 0 auto;
        width: 260px;
        display: grid;
        grid-template-columns: repeat(4, 60px);
        grid-template-rows: repeat(4, 60px);
        gap: 6px;
        background-color: #ffe2b5;
        padding: 12px;
        border-radius: 10px;
      }
      .cell {
        width: 60px;
        height: 60px;
        display: flex;
        justify-content: center;
        align-items: center;
        background-color: #eee;
        color: #333;
        border-radius: 3px;
        font-weight: bold;
      }
      .cell.empty {
        background-color: transparent;
      }
      .cell.value-2 {
        background-color: #eee4da;
        color: #776e65;
      }
      .cell.value-4 {
        background-color: #ede0c8;
        color: #f9f6f2;
      }
      .cell.value-8 {
        background-color: #f2b179;
        color: #f9f6f2;
      }
      .cell.value-16 {
        background-color: #f59563;
        color: #f9f6f2;
      }
      .cell.value-32 {
        background-color: #f67c5f;
        color: #f9f6f2;
      }
      .cell.value-64 {
        background-color: #f65e3b;
        color: #f9f6f2;
      }
      .cell.value-128 {
        background-color: #edcf72;
        color: #f9f6f2;
      }
      .cell.value-256 {
        background-color: #edcc61;
        color: #f9f6f2;
      }
      .cell.value-512 {
        background-color: #edc850;
        color: #f9f6f2;
      }
      .cell.value-1024 {
        background-color: #edc53f;
        color: #f9f6f2;
      }
      .cell.value-2048 {
        background-color: #edc22e;
        color: #f9f6f2;
      }
      .button-container {
        margin-top: 20px;
      }
      .button-container button {
        background-color: #f57c00;
        color: white;
        font-size: 10px;
        font-family: 'avenir', sans-serif;
        border: none;
        border-radius: 3px;
        padding: 10px 20px;
        cursor: pointer;
        margin-right: 3px;
        transition: background-color 0.2s ease, transform 0.2s ease;
      }
      .button-container button:hover {
        background-color: #fb8c00;
        transform: scale(1.05);
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>2048 Game</h1>
      <div class="grid"></div>
      <div class="button-container">
        <button onclick="move('up')">上</button>
        <button onclick="move('down')">下</button>
        <button onclick="move('left')">左</button>
        <button onclick="move('right')">右</button>
        <button onclick="startAutoPlay()">AI 玩</button>
        <button onclick="startAutoPlayFast()">AI 快速玩</button>
        <button onclick="stopAutoPlay()">停止AI</button>
      </div>
    </div>

    <script>
      const gridSize = 4;
      let grid = Array.from({ length: gridSize }, () => Array(gridSize).fill(0));
      let gameOver = false;
      let autoPlayInterval;

      function initGame() {
        addRandomTile();
        addRandomTile();
        render();
        document.addEventListener('keydown', handleKeyPress);
      }

      function addRandomTile() {
        const emptyCells = [];
        for (let i = 0; i < gridSize; i++) {
          for (let j = 0; j < gridSize; j++) {
            if (grid[i][j] === 0) {
              emptyCells.push([i, j]);
            }
          }
        }
        if (emptyCells.length > 0) {
          const [row, col] = emptyCells[Math.floor(Math.random() * emptyCells.length)];
          grid[row][col] = Math.random() < 0.9 ? 2 : 4;
        }
      }

      function render() {
        const gridContainer = document.querySelector('.grid');
        gridContainer.innerHTML = '';
        for (let row of grid) {
          for (let cell of row) {
            const div = document.createElement('div');
            div.classList.add('cell');
            if (cell !== 0) {
              div.classList.add(`value-${cell}`);
              div.textContent = cell;
            } else {
              div.classList.add('empty');
            }
            gridContainer.appendChild(div);
          }
        }
        if (checkGameOver()) {
          alert('Game Over!');
          gameOver = true;
          stopAutoPlay();
        }
      }

      function move(direction) {
        if (gameOver) return;
        const originalGrid = JSON.parse(JSON.stringify(grid));
        switch (direction) {
          case 'up':
            for (let col = 0; col < gridSize; col++) {
              let merged = mergeCells(grid.map((row) => row[col]));
              for (let row = 0; row < gridSize; row++) {
                grid[row][col] = merged[row];
              }
            }
            break;
          case 'down':
            for (let col = 0; col < gridSize; col++) {
              let merged = mergeCells(grid.map((row) => row[col]).reverse()).reverse();
              for (let row = 0; row < gridSize; row++) {
                grid[row][col] = merged[row];
              }
            }
            break;
          case 'left':
            for (let row = 0; row < gridSize; row++) {
              grid[row] = mergeCells(grid[row]);
            }
            break;
          case 'right':
            for (let row = 0; row < gridSize; row++) {
              grid[row] = mergeCells(grid[row].reverse()).reverse();
            }
            break;
        }
        if (JSON.stringify(originalGrid) !== JSON.stringify(grid)) {
          addRandomTile();
          render();
        }
      }

      function mergeCells(cells) {
        cells = cells.filter((cell) => cell !== 0);
        for (let i = 0; i < cells.length - 1; i++) {
          if (cells[i] === cells[i + 1]) {
            cells[i] *= 2;
            cells.splice(i + 1, 1);
          }
        }
        while (cells.length < gridSize) {
          cells.push(0);
        }
        return cells;
      }

      function checkGameOver() {
        for (let i = 0; i < gridSize; i++) {
          for (let j = 0; j < gridSize; j++) {
            if (grid[i][j] === 0) return false;
            if (i < gridSize - 1 && grid[i][j] === grid[i + 1][j]) return false;
            if (j < gridSize - 1 && grid[i][j] === grid[i][j + 1]) return false;
          }
        }
        return true;
      }

      function handleKeyPress(event) {
        switch (event.key) {
          case 'w':
          case 'ArrowUp':
            move('up');
            break;
          case 's':
          case 'ArrowDown':
            move('down');
            break;
          case 'a':
          case 'ArrowLeft':
            move('left');
            break;
          case 'd':
          case 'ArrowRight':
            move('right');
            break;
        }
      }

      function startAutoPlay() {
        stopAutoPlay();
        autoPlayInterval = setInterval(() => {
          const bestMove = getBestMove();
          if (bestMove) {
            move(bestMove);
          }
        }, 400);
      }

      function startAutoPlayFast() {
        stopAutoPlay();
        autoPlayInterval = setInterval(() => {
          const bestMove = getBestMove();
          if (bestMove) {
            move(bestMove);
          }
        }, 50);
      }

      function stopAutoPlay() {
        clearInterval(autoPlayInterval);
        autoPlayInterval = undefined;
      }

      function getBestMove() {
        const moves = ['up', 'down', 'left', 'right'];
        let bestScore = -Infinity;
        let bestMove = null;

        for (const direction of moves) {
          const newGrid = cloneGrid(grid);
          performMove(newGrid, direction);
          if (!areEqualBoards(newGrid, grid)) {
            // Check if the move is valid
            const score = minimax(newGrid, 3, -Infinity, Infinity, false); // Increased depth to 3
            if (score > bestScore) {
              bestScore = score;
              bestMove = direction;
            }
          }
        }

        return bestMove;
      }

      function cloneGrid(grid) {
        return grid.map((row) => [...row]);
      }

      function performMove(newGrid, direction) {
        switch (direction) {
          case 'up':
            for (let col = 0; col < gridSize; col++) {
              let merged = mergeCells(newGrid.map((row) => row[col]));
              for (let row = 0; row < gridSize; row++) {
                newGrid[row][col] = merged[row];
              }
            }
            break;
          case 'down':
            for (let col = 0; col < gridSize; col++) {
              let merged = mergeCells(newGrid.map((row) => row[col]).reverse()).reverse();
              for (let row = 0; row < gridSize; row++) {
                newGrid[row][col] = merged[row];
              }
            }
            break;
          case 'left':
            for (let row = 0; row < gridSize; row++) {
              newGrid[row] = mergeCells(newGrid[row]);
            }
            break;
          case 'right':
            for (let row = 0; row < gridSize; row++) {
              newGrid[row] = mergeCells(newGrid[row].reverse()).reverse();
            }
            break;
        }
      }

      function evaluateBoard(newGrid) {
        let smoothnessWeight = 0.1;
        let monotonicityWeight = 1.0;
        let cornersWeight = 2.0;
        let emptyCellsWeight = 2.7;
        let highValueIsolationPenaltyWeight = 0.5;
        let maxCellValueBonusWeight = 0.5;
        let cornerChainWeight = 1.0;

        let smoothness = calculateSmoothness(newGrid);
        let monotonicity = calculateMonotonicity(newGrid);
        let corners = calculateCorners(newGrid);
        let emptyCells = countEmptyCells(newGrid);
        let highValueIsolationPenalty = calculateHighValueIsolationPenalty(newGrid);
        let maxCellValueBonus = getMaxCellValue(newGrid);
        let cornerChain = calculateCornerChain(newGrid);

        let score =
          smoothnessWeight * smoothness +
          monotonicityWeight * monotonicity +
          cornersWeight * corners +
          emptyCellsWeight * emptyCells -
          highValueIsolationPenaltyWeight * highValueIsolationPenalty +
          maxCellValueBonusWeight * maxCellValueBonus +
          cornerChainWeight * cornerChain;

        return score;
      }

      function calculateSmoothness(grid) {
        let smoothness = 0;
        for (let x = 0; x < gridSize; x++) {
          for (let y = 0; y < gridSize; y++) {
            if (grid[x][y] != 0) {
              for (let dirX = -1; dirX <= 1; dirX++) {
                for (let dirY = -1; dirY <= 1; dirY++) {
                  if (Math.abs(dirX) == Math.abs(dirY)) continue;
                  let newX = x + dirX;
                  let newY = y + dirY;
                  if (newX >= 0 && newX < gridSize && newY >= 0 && newY < gridSize) {
                    if (grid[newX][newY] != 0) {
                      smoothness -= Math.abs(Math.log2(grid[x][y]) - Math.log2(grid[newX][newY]));
                    }
                  }
                }
              }
            }
          }
        }
        return smoothness;
      }

      function calculateMonotonicity(grid) {
        let totalMonotonicity = 0;

        for (let i = 0; i < gridSize; i++) {
          let current = 0;
          let next = current + 1;
          let increasing = true;
          let decreasing = true;

          while (next < gridSize) {
            while (next < gridSize && grid[i][current] == 0) {
              current++;
              next++;
            }
            while (next < gridSize && grid[i][next] == 0) {
              next++;
            }

            if (next >= gridSize) break;

            if (grid[i][current] > grid[i][next]) {
              decreasing = false;
            } else if (grid[i][current] < grid[i][next]) {
              increasing = false;
            }

            current = next;
            next = current + 1;
          }

          if (increasing) {
            totalMonotonicity += 100;
          } else if (decreasing) {
            totalMonotonicity += 100;
          }
        }

        for (let i = 0; i < gridSize; i++) {
          let current = 0;
          let next = current + 1;
          let increasing = true;
          let decreasing = true;

          while (next < gridSize) {
            while (next < gridSize && grid[current][i] == 0) {
              current++;
              next++;
            }
            while (next < gridSize && grid[next][i] == 0) {
              next++;
            }

            if (next >= gridSize) break;

            if (grid[current][i] > grid[next][i]) {
              decreasing = false;
            } else if (grid[current][i] < grid[next][i]) {
              increasing = false;
            }

            current = next;
            next = current + 1;
          }

          if (increasing) {
            totalMonotonicity += 100;
          } else if (decreasing) {
            totalMonotonicity += 100;
          }
        }

        // Add diagonal monotonicity
        let diag1Increasing = true;
        let diag1Decreasing = true;
        let diag2Increasing = true;
        let diag2Decreasing = true;

        for (let i = 0; i < gridSize - 1; i++) {
          if (grid[i][i] > grid[i + 1][i + 1]) {
            diag1Decreasing = false;
          } else if (grid[i][i] < grid[i + 1][i + 1]) {
            diag1Increasing = false;
          }

          if (grid[i][gridSize - 1 - i] > grid[i + 1][gridSize - 2 - i]) {
            diag2Decreasing = false;
          } else if (grid[i][gridSize - 1 - i] < grid[i + 1][gridSize - 2 - i]) {
            diag2Increasing = false;
          }
        }

        if (diag1Increasing || diag1Decreasing) {
          totalMonotonicity += 100;
        }

        if (diag2Increasing || diag2Decreasing) {
          totalMonotonicity += 100;
        }

        return totalMonotonicity;
      }

      function calculateCorners(grid) {
        const cornerValues = [
          grid[0][0],
          grid[0][gridSize - 1],
          grid[gridSize - 1][0],
          grid[gridSize - 1][gridSize - 1],
        ];

        return Math.max(...cornerValues);
      }

      function countEmptyCells(grid) {
        let count = 0;
        for (let row of grid) {
          for (let cell of row) {
            if (cell === 0) {
              count++;
            }
          }
        }
        return count;
      }

      function calculateHighValueIsolationPenalty(grid) {
        let penalty = 0;
        for (let i = 0; i < gridSize; i++) {
          for (let j = 0; j < gridSize; j++) {
            if (grid[i][j] >= 128) {
              let adjacentCount = 0;
              if (i > 0 && grid[i - 1][j] !== 0) adjacentCount++;
              if (i < gridSize - 1 && grid[i + 1][j] !== 0) adjacentCount++;
              if (j > 0 && grid[i][j - 1] !== 0) adjacentCount++;
              if (j < gridSize - 1 && grid[i][j + 1] !== 0) adjacentCount++;
              if (adjacentCount === 0) {
                penalty += grid[i][j];
              }
            }
          }
        }
        return penalty;
      }

      function getMaxCellValue(grid) {
        let maxValue = 0;
        for (let row of grid) {
          for (let cell of row) {
            if (cell > maxValue) {
              maxValue = cell;
            }
          }
        }
        return maxValue;
      }

      function calculateCornerChain(grid) {
        let chainLength = 0;
        const corners = [
          [0, 0],
          [0, gridSize - 1],
          [gridSize - 1, 0],
          [gridSize - 1, gridSize - 1],
        ];
        const visited = Array.from({ length: gridSize }, () => Array(gridSize).fill(false));

        for (const [x, y] of corners) {
          if (grid[x][y] !== 0 && !visited[x][y]) {
            chainLength = Math.max(chainLength, dfs(x, y, grid, visited));
          }
        }

        return chainLength;
      }

      function dfs(x, y, grid, visited) {
        visited[x][y] = true;
        let chainLength = 1;
        const directions = [
          [-1, 0],
          [1, 0],
          [0, -1],
          [0, 1],
        ];

        for (const [dx, dy] of directions) {
          const nx = x + dx;
          const ny = y + dy;
          if (nx >= 0 && nx < gridSize && ny >= 0 && ny < gridSize && !visited[nx][ny] && grid[nx][ny] !== 0) {
            chainLength += dfs(nx, ny, grid, visited);
          }
        }

        return chainLength;
      }

      function minimax(board, depth, alpha, beta, maximizingPlayer) {
        if (depth === 0 || isTerminalState(board)) {
          return evaluateBoard(board);
        }

        if (maximizingPlayer) {
          let maxEval = -Infinity;
          const possibleMoves = getPossibleMoves(board);
          for (const move of possibleMoves) {
            const newBoard = cloneGrid(board);
            performMove(newBoard, move);
            maxEval = Math.max(maxEval, minimax(newBoard, depth - 1, alpha, beta, false));
            alpha = Math.max(alpha, maxEval);
            if (beta <= alpha) break;
          }
          return maxEval;
        } else {
          let minEval = Infinity;
          const possibleBoards = generatePossibleBoards(board);
          for (const newBoard of possibleBoards) {
            minEval = Math.min(minEval, minimax(newBoard, depth - 1, alpha, beta, true));
            beta = Math.min(beta, minEval);
            if (beta <= alpha) break;
          }
          return minEval;
        }
      }

      function isTerminalState(board) {
        return getPossibleMoves(board).length === 0;
      }

      function getPossibleMoves(board) {
        const moves = [];
        const directions = ['up', 'down', 'left', 'right'];

        for (const direction of directions) {
          const newBoard = cloneGrid(board);
          performMove(newBoard, direction);
          if (!areEqualBoards(board, newBoard)) {
            // Ensure the move changes the board
            moves.push(direction);
          }
        }

        return moves;
      }

      function areEqualBoards(board1, board2) {
        for (let i = 0; i < gridSize; i++) {
          for (let j = 0; j < gridSize; j++) {
            if (board1[i][j] !== board2[i][j]) {
              return false;
            }
          }
        }
        return true;
      }

      function generatePossibleBoards(board) {
        const boards = [];
        const emptyCells = [];

        // Find all empty cells
        for (let i = 0; i < gridSize; i++) {
          for (let j = 0; j < gridSize; j++) {
            if (board[i][j] === 0) {
              emptyCells.push([i, j]);
            }
          }
        }

        // Try placing a tile in each empty cell and create a new board for each possibility
        for (const [x, y] of emptyCells) {
          const newBoard1 = cloneGrid(board);
          newBoard1[x][y] = 2;
          boards.push(newBoard1);

          const newBoard2 = cloneGrid(board);
          newBoard2[x][y] = 4;
          boards.push(newBoard2);
        }

        return boards;
      }

      initGame();
    </script>
  </body>
</html>
